package org.chinacloud.wanghui.jdownload.downloader;

import org.chinacloud.wanghui.jdownload.Downloader;
import sun.misc.Cleaner;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * Created with IntelliJ IDEA.
 * User: wanghui
 * Date: 13-10-31
 * Time: 下午2:46
 * To change this template use File | Settings | File Templates.
 */
public abstract class AbstractDownloader implements Downloader{

    //单个文件下载的总链接数
    private int connectionCount;
    //文件大小
    private long size = -1;
    //单个文件下载的线程数组
    private DownloadConnection[] downloadConnections;
    //保存的文件名字
    protected String savePathString;
    //服务器是否支持断点下载
    protected boolean isSupportRange = false;
    //当前是否在下载
    public boolean running = false;
    //活动的下载链接线程数量
    public  int aliveConnectionCount;
    //随机写文件
    public RandomAccessFile randomAccessFile;
    //文件通道
    public  FileChannel fileChannel;


    @Override
    public boolean isSupportRange() {
        return isSupportRange;
    }
    @Override
    public String getSavePathString() {
        return savePathString;
    }
    //获取活动的下载链接线程
    public int getAliveConnectionCount(){
        return aliveConnectionCount;
    }

    //所有的下载链接线程是否已经结束，中断或者挂起
    @Override
    public boolean isDownloading() {
        return aliveConnectionCount > 0;
    }

    //文件是否下载完成
    @Override
    public boolean isCompleted() {
        return getProgress() == getSize();
    }

    //获取文件大小，这个要根据通信协议来判断。
    @Override
    public long getSize() {
        if(size == -1){
            size = getFileLength();
            if(size == -1){
                System.out.println("文件大小获取错误");
            }
        }
        return size;
    }

    //获取下载的总进度
    @Override
    public long getProgress() {
        long progress = 0;
        for (int i = 0 ; i < connectionCount ; i++){
            progress += downloadConnections[i].bytesRead;
        }
        return progress;
    }


    //删除临时文件
    @Override
    public void deleteSateFile() {
        File file = new File(savePathString + ".jd");
        if(file.exists()){
            file.delete();
        }
    }

    //保存下载信息到文件，临时文件
    @Override
    public void saveState() throws IOException {
        ObjectOutputStream os =
                new ObjectOutputStream(new FileOutputStream(
                        savePathString + ".jd"
                ));
        os.writeUTF(this.getClass().getName());
        os.writeInt(connectionCount);
        for (int i = 0;i < connectionCount;i++){
            os.writeLong(downloadConnections[i].bytesBegin);
            os.writeLong(downloadConnections[i].bytesEnd);
            os.writeLong(downloadConnections[i].bytesRead);
        }
        os.writeUTF(savePathString);
        saveDownloadState(os);
        os.close();
    }

    //读取未完成的下载信息（临时文件），进行断点下载
    @Override
    public void loadState(ObjectInputStream is) throws FileNotFoundException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        connectionCount = is.readInt();
        downloadConnections = new DownloadConnection[connectionCount];
        for (int i = 0; i < connectionCount;i++){
            downloadConnections[i] = new DownloadConnection();
            downloadConnections[i].bytesBegin = is.readLong();
            downloadConnections[i].bytesEnd = is.readLong();
            downloadConnections[i].bytesRead = is.readLong();
        }
        savePathString = is.readUTF();
        loadDownloadState(is);
        is.close();
    }

    //保存未完成的下载状态
    protected abstract void saveDownloadState(ObjectOutputStream os)
        throws IOException;
    //读取未完成的下载状态
    protected abstract void loadDownloadState(ObjectInputStream os)
        throws IOException,ClassNotFoundException;

    //开始下载
    @Override
    public boolean start() throws IOException {
        long len = getSize();
        int count;
        if (len < 5 * 1024 *1024){
            count = 2;
        } else if (len < 100 * 1024 * 1024){
            count = 4;
        }  else {
            count = 8;
        }
        return start(count);
    }


    //停止下载
    @Override
    public void stop() {
        running = false;
        for (int i = 0; i < connectionCount ; i++ ){
            if(downloadConnections[i].is != null){
                synchronized (downloadConnections[i].is){
                    if(downloadConnections[i].is != null){
                        try{
                            downloadConnections[i].is.close();
                        }catch (Exception e){
                            System.out.println(e.getMessage());
                        }
                    }
                }
            }
        }
    }


    @Override
    public boolean start(int connectionCount) throws IOException {
        if(!isSupportRange){
            connectionCount = 1;
        }
        this.connectionCount = connectionCount;
        if(getSize() == -1 || connectionCount <= 0){
            return false;
        }
        long bytesPerConnection = getSize()/connectionCount;//每链接线程需要下载的数据量
        downloadConnections = new DownloadConnection[connectionCount];
        long offset = 0;
        int i = 0;
        for (; i < (connectionCount - 1);i++){
            downloadConnections[i] = new DownloadConnection(offset,offset + bytesPerConnection,this);
            offset += bytesPerConnection;
            downloadConnections[i].connectionName = "" + i;
        }
        downloadConnections[i] = new DownloadConnection(offset,getSize(),this);
        downloadConnections[i].connectionName = "" + i;
        return resume();
    }

    //继续下载
    @Override
    public boolean resume() throws IOException {
        if(getSize() == -1 || connectionCount <= 0){
            return false;
        }

        //如果正在下载的话
        if(isDownloading()){
            //等待100毫秒
            for (int i = 0;i < 10;i++){
                try{
                    Thread.sleep(10);
                    if(!isDownloading()){
                        break;
                    }
                }catch(InterruptedException e){
                    System.out.println(e.getMessage());
                }
            }
        }
        if(isDownloading()){
            return false;
        }
        running = true;
        aliveConnectionCount = connectionCount;
        File file = new File(savePathString);
        randomAccessFile = new RandomAccessFile(file,"rw");
        System.out.println("File open: " +file.getAbsolutePath());
        fileChannel = randomAccessFile.getChannel();
        fileChannel.truncate(getSize());
        for(int i = 0;i < connectionCount; i++){
            downloadConnections[i].start();
        }
        return true;
    }


    //暂停下载
    @Override
    public void pause() {
        for(int i = 0; i < connectionCount;i++){
            if(downloadConnections[i].is != null){
                synchronized (downloadConnections[i].is){
                    if(downloadConnections[i].is != null){
                        try {
                            downloadConnections[i].is.close();
                        }catch(Exception e){
                            System.out.println(e.getMessage());
                        }
                    }
                }
            }
        }
    }

    //获取文件长度，看通信协议
    public abstract long getFileLength();
    //获取下载协议
    public abstract String getProtocal();

    //打开一个链接
    public abstract InputStream openConnection(ConnectionContext context,final  long bytesBegin,final long bytesEnd) throws IOException;
    //关闭一个链接
    protected void closeConnection(ConnectionContext context) throws IOException{}
    //准备好开始一个链接
    protected void onBegin(ConnectionContext context){}
    //强制释放内存映射
    public static void unmapFileChannel(final MappedByteBuffer mappedByteBuffer){
        try{
            if(mappedByteBuffer == null){
                return;
            }
            mappedByteBuffer.force();
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    try{
                        Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner",new Class[0]);
                        getCleanerMethod.setAccessible(true);
                        sun.misc.Cleaner cleaner = (Cleaner) getCleanerMethod.invoke(mappedByteBuffer,new Object[0]);
                    } catch (Exception e) {
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    }
                    return null;
                }
            });
        }catch(Exception e){
            System.out.println(e.getMessage());
        }
    }

}
